package com.violet.myorm.core.execute;

import com.violet.myorm.core.combination.QueryCombination;
import com.violet.myorm.core.condition.LambdaWhere;
import com.violet.myorm.core.condition.SerializableFunction;
import com.violet.myorm.core.condition.Where;
import com.violet.myorm.core.page.Limit;
import com.violet.myorm.core.source.Reflect;
import com.violet.myorm.core.sql.QuerySql;
import com.violet.myorm.utils.LimitUtils;
import com.violet.myorm.utils.SqlUtils;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Query<E>
    extends BaseExecute
    implements QueryCombination<E, Query<E>>
{
    private final Class<E> clazz;
    private final List<Field> fields;
    private LambdaWhere<E> where;

    public Query(Class<E> clazz) {
        this.clazz = clazz;
        this.where = new LambdaWhere<>();
        this.fields = Reflect.getNormFields(clazz);
    }

    @Override
    public Query<E> fields(SerializableFunction<E, ?>... functions) {
        fields.clear();
        for (SerializableFunction<E, ?> function : functions) {
            Field field = Reflect.getFieldByName(clazz, function.getName());
            fields.add(field);
        }
        return this;
    }

    @Override
    public Limit<E> limit(int page, int size) {
        int offset = LimitUtils.getOffset(page, size);
        int total = count();
        int totalPage = LimitUtils.getTotalPage(total, size);
        QuerySql select = SqlUtils.createSelect(clazz, fields)
                .setWhere(where.getWhere())
                .setLimit(offset, size);

        return executeQuery(
            select,
            where.getWhereParams(),
            rs -> new Limit<>(Reflect.mapper(clazz, rs, fields), total, size, page, totalPage)
        );
    }

    @Override
    public int count() {
        QuerySql select = SqlUtils.createSelect(clazz, "count(*)").setWhere(where.getWhere());
        return executeQuery(select, where.getWhereParams(), rs -> {
            try {
                if (rs.next()) return rs.getInt(1);
                return 0;
            }
            catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });
    }

    @Override
    public Query<E> where(LambdaWhere<E> condition) {
        this.where = condition;
        return this;
    }

    @Override
    public List<E> executeQuery() {
        QuerySql select = SqlUtils.createSelect(clazz, fields).setWhere(where.getWhere());
        return executeQuery(select, where.getWhereParams(), rs -> Reflect.mapper(clazz, rs, fields));
    }
}
